#pragma once

#include "Common.hpp"
#include "PageCache.hpp"

// 一般来说，CentralCache只需要有一个，所以设计成单例模式
class CentralCache
{
public:
    // 获取单例模式
    static CentralCache *GetInstance()
    {
        return &Cen_Cache_;
    }

    // 从 CentralCache获取一个非空的span
    Span *GetOneSpan(SpanList &list, size_t size)
    {
        // 遍历SpanList，如果有空闲的Span，就直接返回
        Span *it = list.Begin();
        while (it != list.End())
        {
            if (it->freelist_ != nullptr)
            {
                return it;
            }
            else
            {
                it = it->next_;
            }
        }

        // 在这将 CentralCache 的桶锁释放。这样如果其他线程释放内存对象还回来，就不会阻塞了
        list.mtx_.unlock();

        // 遍历SpanList后，如果没有空闲的Span，就向 PageCache 申请Span
        PageCache::GetInstance()->Get_PageMutex().lock();
        Span *span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
        span->isUse_ = true; // 该span已经被使用
        span->objSize_ = size;
        PageCache::GetInstance()->Get_PageMutex().unlock();

        // 记录新 Span 的起始位置和结束位置
        char *start = (char *)(span->pageId_ << PAGE_SHIFT);
        char *end = start + (span->n_ << PAGE_SHIFT);

        // 将大块内存切成小块，用自由链表连接起来
        span->freelist_ = start;
        start += size;
        void *tail = span->freelist_;
        while (start != end)
        {
            *(void **)tail = start;
            tail = *(void **)tail;
            start += size;
        }

        *(void **)tail = nullptr; // *将最后一个对象的next置为nullptr，防止越界访问

        list.mtx_.lock();
        list.PushFront(span);

        return span;
    }

    // 返回值是CentralCache实际给ThreadCache的数量
    size_t FetchRangeObj(void *&start, void *&end, size_t batchNum, size_t size)
    {
        size_t index = SizeClass::Index(size);
        spanlists_[index].mtx_.lock();

        Span *span = GetOneSpan(spanlists_[index], size); // 从非空的 Span 下返回内存给 ThreadCache
        assert(span);
        assert(span->freelist_);

        start = span->freelist_;
        end = start;
        size_t i = 0;
        size_t actulNum = 1;
        while (i < batchNum - 1 && *(void **)end != nullptr)
        {
            end = *(void **)end;
            i++;
            actulNum++;
        }
        span->freelist_ = *(void **)end;
        *(void **)end = nullptr;
        span->useCount_ += actulNum;

        spanlists_[index].mtx_.unlock();
        return actulNum;
    }

    // 将ThreadCache还回来的内存对象挂到对应的span下面
    void ReleaseListToSpans(void *start, size_t size)
    {
        size_t index = SizeClass::Index(size);
        spanlists_[index].mtx_.lock(); // 加上桶锁

        while (start)
        {
            Span *span = PageCache::GetInstance()->MapObjectToSpan(start); // 确定内存对象属于哪个Span
            void *next = *(void **)start;
            *(void **)start = span->freelist_;
            span->freelist_ = start;
            span->useCount_--;

            // 说明span的切分出去的所有小块内存都回来了
            // 这个span就可以再回收给pagecache，pagecache可以再尝试去做前后页的合并
            if (span->useCount_ == 0)
            {
                spanlists_[index].Erase(span); // 把这个Span从桶里面拿掉
                span->freelist_ = nullptr;
                span->next_ = nullptr;
                span->prev_ = nullptr;

                // 将桶锁释放，让其他ThreadCache可以还内存对象给该桶
                spanlists_[index].mtx_.unlock();

                PageCache::GetInstance()->Get_PageMutex().lock();       // 加上页锁
                PageCache::GetInstance()->ReleaseSpanToPageCache(span); // 将空闲的Span还给PageCache
                PageCache::GetInstance()->Get_PageMutex().unlock();     // 释放页锁

                // 重新加上桶锁
                spanlists_[index].mtx_.lock();
            }

            start = next;
        }

        spanlists_[index].mtx_.unlock();
    }

private:
    // 单例模式：将构造函数和拷贝构造函数私有
    CentralCache() {}
    CentralCache(const CentralCache &) = delete;

private:
    SpanList spanlists_[NFREELIST];
    static CentralCache Cen_Cache_;
};

CentralCache CentralCache::Cen_Cache_;